package com.fa4j.code.mate.service.impl;

import com.fa4j.code.mate.convert.CodeMateConverter;
import com.fa4j.code.mate.model.*;
import com.fa4j.code.mate.repository.CodeMateRepository;
import com.fa4j.code.mate.service.CodeMateService;
import com.fa4j.common.base.exception.CommonRequestError;
import com.fa4j.common.base.model.RemoveCmd;
import com.fa4j.common.base.util.ValidUtil;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 代码元数据 服务接口实现
 *
 * @since 2025-09-14 11:40:42
 */
@Slf4j
@Service
@Validated
@RequiredArgsConstructor
public class CodeMateServiceImpl implements CodeMateService {

    private final CodeMateRepository codeMateRepository;
    private final CodeMateConverter codeMateConverter;

    /**
     * 新增代码元数据项目
     *
     * @param cmd 新增参数
     * @return 元数据项目
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CodeMateProject createProject(CodeMateProjectCreateCmd cmd) {
        ValidUtil.validInUsed("项目名称", () -> codeMateRepository.findByTypeAndProjectName(CodeMateType.PROJECT, cmd.getProjectName()));
        checkModuleInUsed(cmd.getModuleName(), cmd.getModuleCode(), null);
        CodeMateProject project = codeMateConverter.toProject(cmd);
        saveAction(project, codeMateRepository::create);
        return project;
    }


    private <T extends AbstractCodeMate> void saveAction(T obj, Consumer<CodeMate> consumer) {
        CodeMate entity = codeMateConverter.toEntity(obj);
        consumer.accept(entity);
        codeMateConverter.updateObj(entity, obj);
    }

    private void checkModuleInUsed(String moduleName, Integer moduleCode, Integer ignoreId) {
        if (!ObjectUtils.isEmpty(moduleName)) {
            ValidUtil.validInUsed("模块名称",
                    () -> codeMateRepository.findByTypesAndModuleName(List.of(CodeMateType.PROJECT, CodeMateType.MODULE), moduleName)
                            .filter(entity -> !Objects.equals(entity.getId(), ignoreId))
            );
        }

        if (!ObjectUtils.isEmpty(moduleCode)) {
            ValidUtil.validInUsed("模块编码",
                    () -> codeMateRepository.findByTypesAndModuleCode(List.of(CodeMateType.PROJECT, CodeMateType.MODULE), moduleCode)
                            .filter(entity -> !Objects.equals(entity.getId(), ignoreId))
            );
        }
    }

    /**
     * 编辑代码元数据项目
     *
     * @param cmd 新增参数
     * @return 元数据项目
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CodeMateProject modifyProject(CodeMateProjectModifyCmd cmd) {
        CodeMateProject project = findById(cmd.getId(), CodeMateType.PROJECT, codeMateConverter::toProject);

        checkModuleInUsed(cmd.getModuleName(), cmd.getModuleCode(), project.getId());

        if (!ObjectUtils.isEmpty(cmd.getProjectName()) && !Objects.equals(cmd.getProjectName(), project.getProjectName())) {
            codeMateRepository.modifyByProjectName(cmd.getProjectName(), project.getProjectName(), cmd.getOperatorId());
        }
        codeMateConverter.updateProject(cmd, project);
        saveAction(project, codeMateRepository::modify);

        return project;
    }

    private <T extends AbstractCodeMate> T findById(Integer id, CodeMateType type, Function<CodeMate, T> function) {
        return codeMateRepository.findById(id)
                .filter(entity -> entity.getType() == type)
                .map(function)
                .orElseThrow(() -> CommonRequestError.NOT_FOUND.build(type.getName()));
    }

    /**
     * 新增代码元数据模块
     *
     * @param cmd 新增参数
     * @return 元数据模块
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CodeMateModule createModule(CodeMateModuleCreateCmd cmd) {
        CodeMateProject project = findById(cmd.getParentId(), CodeMateType.PROJECT, codeMateConverter::toProject);
        checkModuleInUsed(cmd.getModuleName(), cmd.getModuleCode(), null);

        CodeMateModule module = codeMateConverter.toModule(cmd);
        module.setParentId(project.getId());
        module.setProjectName(project.getProjectName());
        saveAction(module, codeMateRepository::create);
        return module;
    }

    /**
     * 编辑代码元数据模块
     *
     * @param cmd 新增参数
     * @return 元数据模块
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CodeMateModule modifyModule(CodeMateModuleModifyCmd cmd) {
        CodeMateModule module = findById(cmd.getId(), CodeMateType.MODULE, codeMateConverter::toModule);
        checkModuleInUsed(cmd.getModuleName(), cmd.getModuleCode(), module.getId());
        if (!ObjectUtils.isEmpty(cmd.getModuleCode()) && !Objects.equals(cmd.getModuleCode(), module.getModuleCode())) {
            codeMateRepository.modifyByModuleCode(cmd.getModuleCode(), module.getModuleCode(), cmd.getOperatorId());
        }
        if (!ObjectUtils.isEmpty(cmd.getModuleName()) && !Objects.equals(cmd.getModuleName(), module.getModuleName())) {
            codeMateRepository.modifyByModuleName(cmd.getModuleName(), module.getModuleName(), cmd.getOperatorId());
        }
        codeMateConverter.updateModule(cmd, module);
        saveAction(module, codeMateRepository::modify);
        return module;
    }

    /**
     * 新增模型
     *
     * @param cmd 新增参数
     * @return 模型
     */
    @Override
    public CodeMateModel createModel(CodeMateModelCreateCmd cmd) {
        CodeMateModule module = findById(cmd.getParentId(), CodeMateType.MODULE, codeMateConverter::toModule);
        ValidUtil.validInUsed("模型名称", () -> codeMateRepository.findByProjectNameAndTypeAndName(module.getProjectName(), CodeMateType.MODEL, cmd.getName()));
        CodeMateModel model = codeMateConverter.toModel(cmd);
        updateParent(model, module);
        saveAction(model, codeMateRepository::create);
        return model;
    }

    private void updateParent(AbstractCodeMate mate, AbstractCodeMate parent) {
        mate.setParentId(parent.getId());
        mate.setProjectName(parent.getProjectName());
        mate.setModuleCode(parent.getModuleCode());
        mate.setModuleName(parent.getModuleName());
    }

    /**
     * 编辑模型
     *
     * @param cmd 编辑参数
     * @return 模型
     */
    @Override
    public CodeMateModel modifyModel(CodeMateModelModifyCmd cmd) {
        CodeMateModel model = findById(cmd.getId(), CodeMateType.MODEL, codeMateConverter::toModel);
        ValidUtil.validInUsed("模型名称", () -> codeMateRepository.findByProjectNameAndTypeAndName(model.getProjectName(), CodeMateType.MODEL, cmd.getName())
                .filter(m -> !m.getId().equals(cmd.getId())));
        codeMateConverter.updateModel(cmd, model);
        saveAction(model, codeMateRepository::modify);
        return model;
    }

    /**
     * 新增模型方法
     *
     * @param cmd 新增参数
     * @return 模型方法
     */
    @Override
    public CodeMateMethod createMethod(CodeMateMethodCreateCmd cmd) {
        ValidUtil.validInUsed("方法名", () -> codeMateRepository.findByParentIdAndName(cmd.getParentId(), cmd.getName()));
        CodeMateModel model = findById(cmd.getParentId(), CodeMateType.MODEL, codeMateConverter::toModel);
        CodeMateMethod method = codeMateConverter.toMethod(cmd);
        updateParent(method, model);
        saveAction(method, codeMateRepository::create);
        return method;
    }


    /**
     * 编辑模型方法
     *
     * @param cmd 编辑参数
     * @return 模型方法
     */
    @Override
    public CodeMateMethod modifyMethod(CodeMateMethodModifyCmd cmd) {

        CodeMateMethod method = findById(cmd.getId(), CodeMateType.METHOD, codeMateConverter::toMethod);
        ValidUtil.validInUsed("方法名", () ->
                codeMateRepository.findByParentIdAndName(method.getParentId(), cmd.getName()).filter(m -> !m.getId().equals(cmd.getId())));
        codeMateConverter.updateMethod(cmd, method);
        saveAction(method, codeMateRepository::modify);
        return method;
    }

    /**
     * 新增模型方法
     *
     * @param cmd 新增参数
     * @return 模型字段
     */
    @Override
    public CodeMateField createField(CodeMateFieldCreateCmd cmd) {
        ValidUtil.validInUsed("字段名", () -> codeMateRepository.findByParentIdAndName(cmd.getParentId(), cmd.getName()));
        CodeMate codeMate = codeMateRepository.findById(cmd.getParentId())
                .filter(m -> m.getType() == CodeMateType.MODEL || m.getType() == CodeMateType.METHOD)
                .orElseThrow(() -> CommonRequestError.NOT_FOUND.build("方法或者模型"));
        CodeMateField field = codeMateConverter.toField(cmd);
        updateParent(field, codeMate);
        saveAction(field, codeMateRepository::create);
        return field;
    }

    /**
     * 编辑模型字段
     *
     * @param cmd 编辑参数
     * @return 模型字段
     */
    @Override
    public CodeMateField modifyField(CodeMateFieldModifyCmd cmd) {
        CodeMateField field = findById(cmd.getId(), CodeMateType.FIELD, codeMateConverter::toField);
        ValidUtil.validInUsed("字段名", () -> codeMateRepository.findByParentIdAndName(field.getParentId(), cmd.getName()).filter(m -> !Objects.equals(cmd.getId(), m.getId())));
        codeMateConverter.updateField(cmd, field);
        saveAction(field, codeMateRepository::modify);
        return field;
    }

    /**
     * 删除代码元数据
     *
     * @param cmd 删除参数
     * @return 代码元数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CodeMate remove(@Valid RemoveCmd<Integer> cmd) {
        CodeMate entity = codeMateRepository.findById(cmd.getId())
                .orElseThrow(() -> CommonRequestError.NOT_FOUND.build("代码元数据"));
        // 当前和下级全部删除
        List<CodeMate> list = queryFlatList(entity);
        for (CodeMate codeMate : list) {
            codeMate.setOperatorId(cmd.getOperatorId());
            codeMateRepository.remove(codeMate);
        }
        codeMateRepository.remove(entity);
        return entity;
    }

    private List<CodeMate> queryFlatList(CodeMate entity) {
        List<CodeMate> result = new ArrayList<>();
        if (entity == null || entity.getType() == CodeMateType.FIELD || entity.getType() == CodeMateType.METHOD) {
            result.add(entity);
        } else {
            List<CodeMate> children = codeMateRepository.queryByParentId(entity.getId());
            for (CodeMate child : children) {
                result.addAll(queryFlatList(child));
            }
        }
        return result;
    }

}




